#include "tex/tex_bitmap.h"
#include "res/res_font.h"
#include "frame.h"
#include "render/r_frustum.h"
#include "file/f_stream.h"
#include "tex/tex_bmp.h"
#include "text.h"

G3::FONT Font1;
G3::TEXTBOX TextBox, TextBox2;

G3::FRAME frame (640, 480, 24, 0);

G3::FPS_COUNTER FPSCounter;

// frustumcheck
G3::AABB3F bboxes [25];
int rendered_bboxes = 0;

// intersectiontests
#define NUM_POLYS   12
G3::VEC3F start, end;
G3::POLYGON3F polys [NUM_POLYS];
G3::INTERSECTION3F intersection;

void init_frustumcheck () {
    int i, j;
    float dist_f = 5.0f;
    G3::VEC3F pos (2, 0, 2);

    for (i=0; i<5; i++) {
        for (j=0; j<5; j++) {
            bboxes [5 * i + j].Minimum = G3::VEC3F (dist_f * j, -0.5f, dist_f * i) + pos;
            bboxes [5 * i + j].Maximum = bboxes [5 * i + j].Minimum + G3::VEC3F (1, 1, 1);
        }
    }
}

void update_frustumcheck () {
    G3::FRUSTUM_CLASSIFIC classific;
    rendered_bboxes = 0;

    for (int i=0; i<25; i++) {
        classific = frame.Renderer.Camera.ViewFrustum.IsBoxIn (bboxes [i]);
        if (classific == G3::GFC_IN) {
            bboxes [i].Render (G3::COL3F (1.0f, 0.0f, 0.0f));
            rendered_bboxes++;
        } if (classific == G3::GFC_HALF) {
            bboxes [i].Render (G3::COL3F (0.0f, 1.0f, 0.0f));

            rendered_bboxes++;
        }
    }
}

void init_intcheck () {
    int i, j;
    G3::VEC3F pos (-2, 0.5, 0);
    G3::VEC3F dist (0.0f, 0.0f, 2.0f);

    // Hexagon to be triangulated
    polys [0].Vertices.push_back (G3::VEC3F (-0.5f, -0.5f, 0.0f) + pos);
    polys [0].Vertices.push_back (G3::VEC3F (-1.0f, 0.0f, 0.0f) + pos);
    polys [0].Vertices.push_back (G3::VEC3F (-0.5f, 0.5f, 0.0f) + pos);
    polys [0].Vertices.push_back (G3::VEC3F (0.5f, 0.5f, 0.0f) + pos);
    polys [0].Vertices.push_back (G3::VEC3F (1.0f, 0.0f, 0.0f) + pos);
    polys [0].Vertices.push_back (G3::VEC3F (0.5f, -0.5f, 0.0f) + pos);

    // Triangulation
    std::vector<G3::POLYGON3F> polylist = G3::TriangulateRegPoly (polys [0]);

    for (i=0; i<(int)polylist.size (); i++) {
        // Shift the triangulated hexagon by dist
        for (j=0; j<(int)polylist [i].Vertices.size (); j++) {
            polylist [i].Vertices [j] += dist;
        }

        polys [i + 1].Vertices = polylist [i].Vertices;
    }

    // Triangle to be intersection-tested
    polys [5].Vertices.push_back (G3::VEC3F (-0.5f, -0.5f, 0.0f) + pos + dist * 2.0f);
    polys [5].Vertices.push_back (G3::VEC3F (0.0f, 0.5f, 0.0f) + pos + dist * 2.0f);
    polys [5].Vertices.push_back (G3::VEC3F (0.5f, -0.5f, 0.0f) + pos + dist * 2.0f);
    polys [5].CalcPlane ();

    // Triangles on 3 planes
    polys [6].Vertices.push_back (G3::VEC3F (-0.5f, -0.5f, 0.0f) + pos + dist * 3.0f);
    polys [6].Vertices.push_back (G3::VEC3F (0.0f, 0.5f, 0.0f) + pos + dist * 3.0f);
    polys [6].Vertices.push_back (G3::VEC3F (0.5f, -0.5f, 0.0f) + pos + dist * 3.0f);
    polys [6].CalcPlane ();
    polys [7].Vertices.push_back (G3::VEC3F (-0.5f, 0.0f, -0.5f) + pos + dist * 3.0f);
    polys [7].Vertices.push_back (G3::VEC3F (0.0f, 0.0f, 0.5f) + pos + dist * 3.0f);
    polys [7].Vertices.push_back (G3::VEC3F (0.5f, 0.0f, -0.5f) + pos + dist * 3.0f);
    polys [7].CalcPlane ();
    polys [8].Vertices.push_back (G3::VEC3F (0.0f, -0.5f, -0.5f) + pos + dist * 3.0f);
    polys [8].Vertices.push_back (G3::VEC3F (0.0f, 0.0f, 0.5f) + pos + dist * 3.0f);
    polys [8].Vertices.push_back (G3::VEC3F (0.0f, 0.5f, -0.5f) + pos + dist * 3.0f);
    polys [8].CalcPlane ();

    // Polygon-Plane classification
    polys [9].Vertices.push_back (G3::VEC3F (-0.5f, -0.5f, 0.0f) + pos + dist * 4.0f);
    polys [9].Vertices.push_back (G3::VEC3F (0.0f, 0.5f, 0.0f) + pos + dist * 4.0f);
    polys [9].Vertices.push_back (G3::VEC3F (0.5f, -0.5f, 0.0f) + pos + dist * 4.0f);
    polys [9].CalcPlane ();

    // Regression distance check
    polys [10].Vertices.push_back (G3::VEC3F (-0.5f, -0.5f, 0.0f) + pos + dist * 5.0f);
    polys [10].Vertices.push_back (G3::VEC3F (0.0f, 0.5f, 0.0f) + pos + dist * 5.0f);
    polys [10].Vertices.push_back (G3::VEC3F (0.5f, -0.5f, 0.0f) + pos + dist * 5.0f);
    polys [11].Vertices.push_back (G3::VEC3F (0.7f, -0.5f, 0.0f) + pos + dist * 5.0f);
    polys [11].Vertices.push_back (G3::VEC3F (1.2f, 0.5f, 0.0f) + pos + dist * 5.0f);
    polys [11].Vertices.push_back (G3::VEC3F (0.95f, 0.0f, 0.0f) + pos + dist * 5.0f);
}

void update_intcheck () {
    int i, j;
    G3::RAY3F ray (start, end);
    G3::POLYGON3F front, back;
    G3::HALFSPACE hspace;

    intersection = G3::RayTriIntersection (polys [5].Plane, polys [5].Vertices [0], polys [5].Vertices [1], polys [5].Vertices [2], ray);

    glDisable (GL_TEXTURE_2D);
    glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

    for (i=0; i<NUM_POLYS; i++) {
        if (!polys [i].Vertices.empty ()) {
            // We'll render those later
            if (i == 7 || i == 8)
                continue;

            glColor3f (0.0f, 1.0f, 1.0f);

            // Triangle intersection test ?
            if (i == 5) {
                // Line segment intersects the triangle ?
                if (G3_TESTFLAG (intersection.Flags, G3::INTERSECTION::INT_POLY))
                    glColor3f (1.0f, 0.0f, 0.0f);
                // Line segment intersects plane ?
                else if (G3_TESTFLAG (intersection.Flags, G3::INTERSECTION::INT_LINE_FRAG))
                    glColor3f (0.0f, 1.0f, 0.0f);
                // Parallel ?
                else if (!G3_TESTFLAG (intersection.Flags, G3::INTERSECTION::INT_RAY))
                    glColor3f (0.0f, 0.0f, 1.0f);
            }

            // Polygon-Plane classification test ?
            if (i == 9) {
                hspace = G3::PlaneClassifyPolygon (polys [i].Plane, polys [7]);

                if (hspace == G3::NEGATIVE)
                    glColor3f (0.0f, 0.0f, 1.0f);
                else if (hspace == G3::POSITIVE)
                    glColor3f (0.0f, 1.0f, 0.0f);
                else
                    glColor3f (1.0f, 0.0f, 0.0f);
            }

            // Regression distance check ?
            if (i == 10 || i == 11) {
                // Red, if this triangle is considered degenerate
                if (G3::MinDistRegression (polys [i]) < G3_EPSILON)
                    glColor3f (1.0f, 0.0f, 0.0f);
            }

            glBegin (GL_POLYGON);
                for (j=0; j<(int)polys [i].Vertices.size (); j++)
                    glVertex3f (polys [i].Vertices [j].x, polys [i].Vertices [j].y, polys [i].Vertices [j].z);
            glEnd ();
        }
    }

    // Test polygon splitting
    G3::SplitPolygonByPlane (polys [7], polys [6].Plane, &front, &back);

    if (!front.Vertices.empty ()) {
        glColor3f (0.0f, 1.0f, 1.0f);

        glBegin (GL_POLYGON);
            for (j=0; j<(int)front.Vertices.size (); j++)
                glVertex3f (front.Vertices [j].x, front.Vertices [j].y, front.Vertices [j].z);
        glEnd ();
    }

    if (!back.Vertices.empty ()) {
        glColor3f (0.0f, 0.5f, 0.5f);

        glBegin (GL_POLYGON);
            for (j=0; j<(int)back.Vertices.size (); j++)
                glVertex3f (back.Vertices [j].x, back.Vertices [j].y, back.Vertices [j].z);
        glEnd ();
    }

    G3::SplitPolygonByPlane (polys [8], polys [6].Plane, &front, &back);

    if (!front.Vertices.empty ()) {
        glColor3f (0.0f, 1.0f, 1.0f);

        glBegin (GL_POLYGON);
            for (j=0; j<(int)front.Vertices.size (); j++)
                glVertex3f (front.Vertices [j].x, front.Vertices [j].y, front.Vertices [j].z);
        glEnd ();
    }

    if (!back.Vertices.empty ()) {
        glColor3f (0.0f, 0.5f, 0.5f);

        glBegin (GL_POLYGON);
            for (j=0; j<(int)back.Vertices.size (); j++)
                glVertex3f (back.Vertices [j].x, back.Vertices [j].y, back.Vertices [j].z);
        glEnd ();
    }

    glPolygonMode (GL_FRONT_AND_BACK, GL_FILL);

    // Ray line
    glColor3f (0.5f, 0.5f, 0.5f);

    glBegin (GL_LINES);
        glVertex3f (start.x, start.y, start.z);
        glVertex3f (end.x, end.y, end.z);
    glEnd ();

    // Intersection point on a triangle
    glEnable (GL_POINT_SMOOTH);
    glPointSize (10.0f);

    glColor3f (1.0f, 1.0f, 1.0f);

    glBegin (GL_POINTS);
        glVertex3f (intersection.Point.x, intersection.Point.y, intersection.Point.z);
    glEnd ();

    // Intersection point of 3 planes
    intersection = G3::Intersect3Planes (polys [6].Plane, polys [7].Plane, polys [8].Plane);

    if (G3_TESTFLAG (intersection.Flags, G3::INTERSECTION::INT_PLANES))
        glColor3f (1.0f, 0.0f, 0.0f);
    else
        glColor3f (0.0f, 1.0f, 0.0f);

    glBegin (GL_POINTS);
        glVertex3f (intersection.Point.x, intersection.Point.y, intersection.Point.z);
    glEnd ();

    glEnable (GL_TEXTURE_2D);
}

void UTUpdateHandler (G3::INPUT *Input, float aElapsedTime) {
    int i;

	if (Input->Keyboard.IsKeyDown (SDLK_a))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 1.0f, 0.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_z))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, -1.0f, 0.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_LEFT))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (-1.0f, 0.0f, 0.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_RIGHT))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (1.0f, 0.0f, 0.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_UP))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 0.0f, 1.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_DOWN))
		Input->Frame->Renderer.Camera.Move (G3::VEC3F (0.0f, 0.0f,-1.0f) * aElapsedTime);
	if (Input->Keyboard.IsKeyDown (SDLK_SPACE)) {
		start = Input->Frame->Renderer.Camera.GetWorldPosition ();
		end = start - Input->Frame->Renderer.Camera.GetForwardVector () * 1000.0f;
	}
	if (Input->Keyboard.IsKeyDown (SDLK_e)) {
	    for (i=0; i<(int)polys [7].Vertices.size (); i++)
            polys [7].Vertices [i] += G3::VEC3F (0.0f, 0.0f, 1.0f) * aElapsedTime;
	    for (i=0; i<(int)polys [8].Vertices.size (); i++)
            polys [8].Vertices [i] += G3::VEC3F (0.0f, 0.0f, 1.0f) * aElapsedTime;
	}
	if (Input->Keyboard.IsKeyDown (SDLK_d)) {
	    for (i=0; i<(int)polys [7].Vertices.size (); i++)
            polys [7].Vertices [i] += G3::VEC3F (0.0f, 0.0f, -1.0f) * aElapsedTime;
	    for (i=0; i<(int)polys [8].Vertices.size (); i++)
            polys [8].Vertices [i] += G3::VEC3F (0.0f, 0.0f, -1.0f) * aElapsedTime;
	}
	if (Input->Keyboard.IsKeyDown (SDLK_r)) {
        polys [9].Vertices [2] += G3::VEC3F (0.0f, 0.0f, 1.0f) * aElapsedTime;
	}
	if (Input->Keyboard.IsKeyDown (SDLK_f)) {
	    polys [9].Vertices [2] += G3::VEC3F (0.0f, 0.0f, -1.0f) * aElapsedTime;
	}


	if (Input->Keyboard.IsKeyDown (SDLK_ESCAPE))
		Input->SendQuit ();
}


int main (int argc, char **argv) {
    frame.Create ("Game3ngine");

    frame.Renderer.Create (640, 480);

    frame.Input.Mouse.SetMode (MOUSE_CENTER);

    frame.Input.doOnUpdate (UTUpdateHandler);

    /* Enable smooth shading */
    glShadeModel( GL_SMOOTH );

    /* Set the background black */
    glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );

    /* The Type Of Depth Test To Do */
    glDepthFunc( GL_LEQUAL );

    G3::MainFilesys.MountDir ("../../media/font", "");

    Font1.Load ("font.lua");
    TextBox.Font = &Font1;
    TextBox2.Font = &Font1;

    G3::MainLog.Report ("Font loaded..\n");

    TextBox.Position = G3::VEC2I (10, 10);
    TextBox2.Position = G3::VEC2I (10, 22);

    FPSCounter.Start ();

    init_frustumcheck ();
    init_intcheck ();

    G3::TIMER timer;

    timer.Start ();

    try {
        throw G3::EX ("A test exception..", FILE_LINE);
    } catch (G3::EX ex) {
        printf ("Exception: %s\n", ex.toString ().c_str ());
    }

    while (true) {
        frame.Input.GetInput ();

        frame.Renderer.ClearBuffer ();

        FPSCounter.CheckTime ();

        frame.Renderer.Look ();
        frame.Renderer.RenderGrid ();

        update_frustumcheck ();
        update_intcheck ();

        TextBox.glPrint ("FPS: %.2f BBoxes: %d", FPSCounter.FPS, rendered_bboxes);

        frame.Renderer.SwapBuffer ();

        FPSCounter.AddFrame ();
    }

    Font1.Clear ();

    return 0;
}
